﻿using System.Collections;
using UnityEngine;
using Object = UnityEngine.Object;

namespace TPF.ObjectCache
{
    public abstract class PoolRule
    {
        protected PoolCore PoolCore;

        internal virtual void OnInitialize(PoolCore poolCore)
        {
            PoolCore = poolCore;
        }

        internal virtual void OnObjectActivate(GameObject gameObject, string key)
        {
        }

        internal virtual void OnObjectRecycle(GameObject gameObject, string key)
        {
        }

        internal virtual void OnClear()
        {
        }
    }

    /// <summary>
    /// 按照时间淘汰对象池中的对象
    /// </summary>
    public sealed class LiveTimeRule : PoolRule
    {
        public float CheckInternal { get; set; } // 扫描所有对象是否存活的时间间隔
        public float ObjectLifeTime { get; set; } // 对象存活时间

        public LiveTimeRule()
        {
            // 默认值
            CheckInternal = 5;
            ObjectLifeTime = 120;
        }

        internal override void OnInitialize(PoolCore poolCore)
        {
            base.OnInitialize(poolCore);

            poolCore.StartCoroutine(EndlessLoop());
        }

        private IEnumerator EndlessLoop()
        {
            while (true)
            {
                yield return new WaitForSeconds(CheckInternal);

                CheckObjectsAlive();
            }

            // ReSharper disable once IteratorNeverReturns
        }

        private void CheckObjectsAlive()
        {
            var now = Time.time;
            foreach (var kv in PoolCore.PooledObjects)
            {
                var pooledObjectInfoQueue = kv.Value;

                if (pooledObjectInfoQueue.Count == 0) continue;

                // 筛选出超时的对象
                // 由于池中的对象是用队列保存的，所以队列从头到尾记录的回收时间是逐渐增大的，判断时仅需找到一个不满足条件的对象，其后的对象都不需要再判断
                var head = pooledObjectInfoQueue.Peek();
                while (now - head.RecycleTime > ObjectLifeTime)
                {
                    var removeObjectInfo = pooledObjectInfoQueue.Dequeue();
                    Object.Destroy(removeObjectInfo.GameObject);

                    if (pooledObjectInfoQueue.Count != 0)
                    {
                        head = pooledObjectInfoQueue.Peek();
                    }
                    else
                    {
                        break;
                    }
                }

                // 如果所有池中的对象都被清除，且没有激活的对象，则清除模板
                if (pooledObjectInfoQueue.Count == 0)
                {
                    var needDelete = true;
                    var key = kv.Key;
                    foreach (var activatedObjectInfo in PoolCore.ActivatedObjects.Values)
                    {
                        if (activatedObjectInfo.Key == key)
                        {
                            needDelete = false; // 表示还有使用中的对象，不能将模板删除
                            break;
                        }
                    }

                    if (needDelete)
                    {
                        if (PoolCore.TemplateAssets.ContainsKey(key))
                        {
                            PoolCore.TemplateAssets[key].Release();
                            PoolCore.TemplateAssets.Remove(key);
                        }
                        else
                        {
                            Debug.LogError(string.Format("Asset {0} template is missing", key));
                        }
                    }
                }
            }
        }
    }

    /// <summary>
    /// 按照对象数量来淘汰对象池中的对象
    /// </summary>
    public sealed class AmountLimitRule : PoolRule
    {
        public float MaxAmount { get; set; } // 池中最大对象数量

        public AmountLimitRule()
        {
            // 默认值
            MaxAmount = 10;
        }

        /// <summary>
        /// 有对象回收时检测
        /// </summary>
        /// <param name="gameObject"></param>
        /// <param name="key"></param>
        internal override void OnObjectRecycle(GameObject gameObject, string key)
        {
            base.OnObjectRecycle(gameObject, key);

            // 如果池中的对象数大于指定的数值，则将旧的对象删除
            var pooledObjects = PoolCore.PooledObjects[key];
            while (pooledObjects.Count > MaxAmount)
            {
                Object.Destroy(pooledObjects.Dequeue().GameObject);
            }
        }
    }
}